Un árbol de decisión es una herramienta de apoyo a la decisión que utiliza un modelo en forma de árbol de decisiones y sus posibles consecuencias, incluyendo los resultados resultados, costes de recursos y utilidad. Es una forma de mostrar un algoritmo que sólo contiene declaraciones de control condicional.
Tenemos las calificaciones de un examen de 10 alumnos por medio de la
variable score, luego las horas que dedican de estudio en
hours y sus calificaciones promedio midterm.
Nos preguntamos cuáles son los estudintes que tienen mejores
calificaciones.
Podemos realizar un árbol de decisión simple clasificando los estudiantes por medio de las variables. ¿Cómo realizamos este árbol?
Calculamos la calificación promedio de los 10 estudiantes: 57 y tenemos el 100% de la muestra,
Nos preguntamos, ¿Estudia menos de 10 horas?, si la respuesta es sí, demos que tenemos 5 registros, que corresponden al 50% de los datos, y su calificación promedio es de 39; por otro lado, si la respuesta es no tenemos que su calificación promedio fue de 75.
Con este último grupo, nos hacemos la pregunta, ¿su calificación promedio es menor que 65?, si la respuesta es sí, tenemos 3 estudiantes, que corresponden al 30% de todos los datos, con calificación promedio 70 y si la respuesta es no, tenemos 2 estudaintes, que corresponde al 20% de todos los datos, con califiacción promedio de 82.
| score | hours | midterm | |
|---|---|---|---|
| 1 | 35 | 6 | 42 |
| 2 | 38 | 5 | 65 |
| 3 | 40 | 7 | 35 |
| 4 | 45 | 6 | 75 |
| 5 | 35 | 8 | 60 |
| 6 | 65 | 11 | 50 |
| 7 | 70 | 12 | 45 |
| 8 | 75 | 18 | 40 |
| 9 | 80 | 14 | 80 |
| 10 | 85 | 12 | 82 |
Dividimos el espacio del predictor, es decir, el conjunto de valores posibles para \(X_1,X_2,...,X_p\), en \(J\) regiones distintas y no superpuestas, \(R_1,R_2,...,R_J\).
Para cada observación que cae en la región \(R_j\), hacemos la misma predicción, que es simplemente la media de los valores de respuesta para las observaciones de entrenamiento en \(R_j\).
El objetivo es minimizar el RSS (Residual Sum of Squares):
\[ \sum_{j=1}^J\sum_{i\in R_j} (y_i-\hat{y}_{R_j})^2 \]
Debemos tener algunas consideraciones de estos árboles:
Enfoque descendente y codicioso que se conoce como división binaria recursiva.
Es descendente porque comienza en la parte superior del árbol y luego divide sucesivamente el espacio de predicción
Cada división se indica mediante dos nuevas ramas más abajo en el árbol.
Es codicioso porque en cada paso del proceso de construcción del árbol, se realiza la mejor división en ese paso en particular, en lugar de mirar hacia adelante y elegir una división que conduzca a un mejor árbol en algún paso futuro.
En resumen podemos seguir los siguientes pasos:
Considera todos los predictores y todos los posibles valores del punto de corte
Calcula el RSS para cada posibilidad
Selecciona la que tiene menos RSS
Continúa hasta que se alcanzan los criterios de parada
Árbol de regresión: Para la variable objetivo cuantitativa continua. Por ejemplo, predicción de precipitaciones, predicción de ingresos, predicción de notas, etc.
Árbol de clasificación: Para variables objetivo categóricas discretas. Ej. Predicción de alto o bajo, victoria o pérdida, saludable o no saludable, etc.
Dividimos el espacio del predictor, es decir, el conjunto de valores posibles para \(X_1,X_2,...,X_p\), en \(J\) regiones distintas y no superpuestas, \(R_1,R_2,...,R_J\).
Para cada observación que cae en la región \(R_j\), hacemos la misma predicción, que es simplemente la media de los valores de respuesta para las observaciones de entrenamiento en \(R_j\).
El objetivo es minimizar el RSS (Residual Sum of Squares):
\[ \sum_{j=1}^J\sum_{i\in R_j} (y_i-\hat{y}_{R_j})^2 \]
Debemos tener algunas consideraciones de estos árboles:
Enfoque descendente y codicioso que se conoce como división binaria recursiva.
Es descendente porque comienza en la parte superior del árbol y luego divide sucesivamente el espacio de predicción
Cada división se indica mediante dos nuevas ramas más abajo en el árbol.
Es codicioso porque en cada paso del proceso de construcción del árbol, se realiza la mejor división en ese paso en particular, en lugar de mirar hacia adelante y elegir una división que conduzca a un mejor árbol en algún paso futuro.
En resumen podemos seguir los siguientes pasos:
Considera todos los predictores y todos los posibles valores del punto de corte
Calcula el RSS para cada posibilidad
Selecciona la que tiene menos RSS
Continúa hasta que se alcanzan los criterios de parada
Árbol de regresión: Para la variable objetivo cuantitativa continua. Por ejemplo, predicción de precipitaciones, predicción de ingresos, predicción de notas, etc.
Árbol de clasificación: Para variables objetivo categóricas discretas. Ej. Predicción de alto o bajo, victoria o pérdida, saludable o no saludable, etc.
Podemos tener distintos criterios, tales como:
Observaciones mínimas en un nodo. Número mínimo de observaciones necesarias para una nueva división.
Observaciones mínimas en el nodo de la hoja. Número mínimo de observaciones necesarias en cada nodo después de la división.
Profundidad máxima. Número máximo de capas del árbol posibles
Para ver cómo construimos un árbol, primero que todo vamos a ver nuestros datos:
library(tidyverse)Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
── Attaching packages ──────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5 ✓ purrr 0.3.4
✓ tibble 3.1.2 ✓ dplyr 1.0.7
✓ tidyr 1.1.3 ✓ stringr 1.4.0
✓ readr 1.4.0 ✓ forcats 0.5.1
── Conflicts ─────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
movie <- read.csv("Movie_regression.csv")
head(movie)colnames(movie) [1] "Marketing.expense" "Production.expense"
[3] "Multiplex.coverage" "Budget"
[5] "Movie_length" "Lead_.Actor_Rating"
[7] "Lead_Actress_rating" "Director_rating"
[9] "Producer_rating" "Critic_rating"
[11] "Trailer_views" "X3D_available"
[13] "Time_taken" "Twitter_hastags"
[15] "Genre" "Avg_age_actors"
[17] "Num_multiplex" "Collection"
Recordemos un poco de exploración de datos usando el paquete
DataExplorer:
library(DataExplorer)Registered S3 method overwritten by 'data.table':
method from
print.data.table
Registered S3 method overwritten by 'htmlwidgets':
method from
print.htmlwidget tools:rstudio
plot_intro(movie)plot_missing(movie)En esta primera parte podemos ver cómo los datos que vamos a analizar no tienen problemas de datos faltantes, para lo cual pusieramos eliminar alguna de las variables.
Para mirar un poco de qué tipos de variables tenemos en nuestro dataset hagamos un resumen de cada una de las variables:
summary(movie) Marketing.expense Production.expense Multiplex.coverage
Min. : 20.13 Min. : 55.92 Min. :0.1290
1st Qu.: 21.64 1st Qu.: 65.38 1st Qu.:0.3760
Median : 25.13 Median : 74.38 Median :0.4620
Mean : 92.27 Mean : 77.27 Mean :0.4453
3rd Qu.: 93.54 3rd Qu.: 91.20 3rd Qu.:0.5510
Max. :1799.52 Max. :110.48 Max. :0.6150
Budget Movie_length Lead_.Actor_Rating
Min. :19781 Min. : 76.4 Min. :3.840
1st Qu.:32694 1st Qu.:118.5 1st Qu.:7.316
Median :34488 Median :151.0 Median :8.307
Mean :34911 Mean :142.1 Mean :8.014
3rd Qu.:36794 3rd Qu.:167.6 3rd Qu.:8.865
Max. :48773 Max. :173.5 Max. :9.435
Lead_Actress_rating Director_rating Producer_rating Critic_rating
Min. :4.035 Min. :3.840 Min. :4.030 Min. :6.600
1st Qu.:7.504 1st Qu.:7.296 1st Qu.:7.508 1st Qu.:7.200
Median :8.495 Median :8.312 Median :8.465 Median :7.960
Mean :8.186 Mean :8.020 Mean :8.191 Mean :7.811
3rd Qu.:9.030 3rd Qu.:8.884 3rd Qu.:9.030 3rd Qu.:8.260
Max. :9.540 Max. :9.425 Max. :9.635 Max. :9.400
Trailer_views X3D_available Time_taken
Min. :212912 Length:506 Min. : 0.0
1st Qu.:409128 Class :character 1st Qu.:132.3
Median :462460 Mode :character Median :160.0
Mean :449861 Mean :157.4
3rd Qu.:500248 3rd Qu.:181.9
Max. :567784 Max. :217.5
NA's :12
Twitter_hastags Genre Avg_age_actors Num_multiplex
Min. : 201.2 Length:506 Min. : 3.00 Min. :333.0
1st Qu.: 223.8 Class :character 1st Qu.:28.00 1st Qu.:465.0
Median : 254.4 Mode :character Median :39.00 Median :535.5
Mean : 260.8 Mean :39.18 Mean :545.0
3rd Qu.: 283.4 3rd Qu.:50.00 3rd Qu.:614.8
Max. :2022.4 Max. :60.00 Max. :868.0
Collection
Min. : 10000
1st Qu.: 34050
Median : 42400
Mean : 45058
3rd Qu.: 50000
Max. :100000
Para realizar esta partición, utilizaremos otra metología usando el
paquete caTools, por lo cual primero debemos de instalarlo
en R por medio del comando install.packages("caTools"). Ya
teniendo la instalación procedemos a cargar la librería y a realizar
nuesta partición:
library(caTools)
set.seed(0) # La semilla
split = sample.split(movie,SplitRatio=0.8) # Los datos de train serán de 80%
train = subset(movie,split == TRUE)
test = subset(movie,split == FALSE)La variable de control para enconetrar el árbol de decisión es
Collection, para lo cual, recordemos que es importante ver
que esta variable se distribuya de forma similar en ambos conjuntos de
train y test:
summary(train$Collection) Min. 1st Qu. Median Mean 3rd Qu. Max.
10000 34400 42400 45247 50000 100000
summary(test$Collection) Min. 1st Qu. Median Mean 3rd Qu. Max.
11200 32200 42400 44398 50600 100000
Para la construcción de nuestro árbol de decisión para un problema de
regresión, nos apoyaremos de los paquete de R: rpart,
rpart.plot.
Lo primero que haremos es cargar las dos librerías luego de haber instalado previamente los paquetes:
library(rpart)
library(rpart.plot)Para este ejemplo, utilizaremos el modelo de regresión que se
encuentra en esta paquetería, donde nuestra variable de interés o
variable objetivo será Colection. El criterio de parada lo
tomaremos por medio de la función rpart.control, donde se
especifica que maxdepth = 3, esto establece que, la
profundidad máxima de un nodo final del árbol, contando desde el nodo
raíz es máximo 3.
# Modelo de regresión con el conjunto de datos train
regtree <- rpart(formula=Collection~.,
data = train,
control = rpart.control(maxdepth = 3))
# Visualización del árbol de decisión
rpart.plot(regtree, box.palette = "RdBu", digits= -3)# Cálculo del predictor
test <- test %>% mutate(pred =predict(regtree, test, type = "vector"),
reg = 1:nrow(test),
dif_pred_coll = Collection-pred)
library(ggplot2)
ggplot(test)+
geom_point(aes(x=reg, y=dif_pred_coll))
MSE2 <- mean((test$pred - test$Collection)^2)Los grandes árboles de decisión (con muchos nodos y muchas divisiones), tienen dos problemas: la primera es que son difíciles de interpretar y la segunda es que sobrealimentan los datos del tren dando así un mal rendimiento de los activos. Una de las formas de controlar esto, es como ya lo hemos estado haciendo: por medio de los criterios de parada.
Otra estrategia que podemos tener es hacer un árbol muy grande, y luego podarlo o cortar algunas partes que no nos benefician en el cálculo del objetivo.
Así que ahora queremos un subárbol con es la prueba más baja a una tasa podemos utilizar la validación cruzada para averiguar la tasa de error de la prueba de todos los subsidios de este tipo, pero esto puede ser computacionalmente caro (va a tardar mucho tiempo).
Por lo tanto, utilizamos un método llamado “poda de complejidad de costes” o “poda de eslabones más despiertos” en la materia.
Añadimos el coste adicional del número de nodos terminales en el árbol al RSS, de modo que en lugar de minimizar las direcciones, minimizamos el RSS más el número de nodos terminales, lo que se denomina parámetro único o parámetro de complejidad.
\[ \sum_{m=1}^{|T|}\sum_{x_i\in R_m}(y_i-\hat{y}_{R_m})^2+\alpha|T| \]
donde \(\alpha\) es el parámetro de complejidad, \(T\) es el número de hojas del árbol.
Estamos minimizando el sistema de orden normal por lo que obtenemos raíces normales del árbol pero el valor de la multa aumenta que es el aumento de la pena por tener más división.
Por lo tanto, el valor de \(\alpha\) controla el crecimiento del árbol, así que el proceso es el siguiente: tenemos que encontrar el valor de \(\alpha\) en el que obtenemos el mínimo error con validación cruzada en nuestro conjunto de entrenamiento, utilizando el valor de \(\alpha\) para podar el árbol y esperamos que este árbol podado se desempeñe mejor en el conjunto de prueba.
Hagamos este procedimiento siguiendo el ejemplo que anterior de las
películas en R. Primero hagamos el árbol completo (con todas las
posibilidades), el cual llamaremos fulltree y donde
cp es control parameter .
# Tree Pruning
fulltree <- rpart(formula=Collection~.,
data = train,
control = rpart.control(cp = 0))
# Visualización del árbol de decisión
rpart.plot(fulltree, box.palette = "RdBu", digits= -3)Podemos ver el comportamiento de éste parámetro de control por medio
la función printcp()
printcp(fulltree)
Regression tree:
rpart(formula = Collection ~ ., data = train, control = rpart.control(cp = 0))
Variables actually used in tree construction:
[1] Avg_age_actors Budget Critic_rating
[4] Director_rating Genre Lead_.Actor_Rating
[7] Lead_Actress_rating Marketing.expense Movie_length
[10] Multiplex.coverage Production.expense Time_taken
[13] Trailer_views Twitter_hastags X3D_available
Root node error: 1.3086e+11/393 = 332989821
n= 393
CP nsplit rel error xerror xstd
1 0.43507653 0 1.00000 1.00273 0.095500
2 0.15658756 1 0.56492 0.58950 0.066680
3 0.09064716 2 0.40834 0.43374 0.054116
4 0.04887218 3 0.31769 0.34122 0.048513
5 0.03105207 4 0.26882 0.36855 0.053490
6 0.02172378 5 0.23776 0.30922 0.046400
7 0.01458513 6 0.21604 0.27922 0.042112
8 0.00977123 7 0.20146 0.28362 0.041988
9 0.00935818 8 0.19168 0.28225 0.041985
10 0.00808528 9 0.18233 0.29373 0.042983
11 0.00793019 10 0.17424 0.29301 0.042983
12 0.00548094 11 0.16631 0.27704 0.041450
13 0.00507787 12 0.16083 0.26325 0.040938
14 0.00476304 13 0.15575 0.26155 0.040922
15 0.00331305 14 0.15099 0.25770 0.040956
16 0.00291437 15 0.14768 0.25791 0.040942
17 0.00263491 16 0.14476 0.25580 0.040943
18 0.00200138 17 0.14213 0.25596 0.041052
19 0.00196853 18 0.14013 0.25447 0.041500
20 0.00153874 19 0.13816 0.25376 0.041501
21 0.00135750 20 0.13662 0.25523 0.041490
22 0.00130090 21 0.13526 0.25438 0.041492
23 0.00122409 22 0.13396 0.25314 0.041493
24 0.00084140 23 0.13274 0.25129 0.041516
25 0.00075770 24 0.13189 0.25177 0.041500
26 0.00074873 25 0.13114 0.25154 0.041487
27 0.00073565 27 0.12964 0.25139 0.041488
28 0.00067572 28 0.12890 0.25220 0.041481
29 0.00047007 29 0.12823 0.25244 0.041454
30 0.00039681 30 0.12776 0.25213 0.041464
31 0.00034292 31 0.12736 0.25272 0.041469
32 0.00028553 32 0.12702 0.25263 0.041472
33 0.00000000 33 0.12673 0.25239 0.041472
Aquí puedes ver los diferentes valores de CP para los cuales
obtenemos diferentes valores de error relativo. El valor de
xerror es el error de validación cruzada.
Queremos encontrar ese valor de CP para el cual la edición de validación cruzada es mínima. Ese valor de CP otro parámetro de ajuste se utilizará para podar el árbol.
Ahora también se puede graficar estos valores de CP y exeter usando
esta función de plotcp y se puede ver que a medida que
seguimos incrementando el valor de CP el error relativo está
inicialmente disminuyendo y luego comienza a aumentar y así
sucesivamente. Por lo tanto, como sube e baja este valor, en algún lugar
de habar un valor para CP para el cual es mínimo (mínimo global).
plotcp(fulltree)Encontremos el mínimo calor de CP. con la siguiente línea vamos a
encontrar el valor de CP para el cual es el error de validación cruzada
xerror es mínimo.
mincp <- regtree$cptable[which.min(regtree$cptable[,"xerror"]),"CP"]
mincp[1] 0.01458513
Usando este parámetro que encontramos como mínimo, ejecutaremos la
función prune() y el mismo árbol será podado.
prunedtree <- prune(fulltree, cp = mincp)
rpart.plot(prunedtree, box.palette = "RdBu", digits = 3)Podemos comparar los resultados con el árbol sin podar y el árbol podado, para esto hacemos el cálculo, tal como lo hicimos en el ejemplo anterior, antes de podar:
test$fulltree <- predict(fulltree, test, type = "vector")
MSE2 <- mean((test$fulltree - test$Collection)^2)
MSE2[1] 99037642
test$pruned <- predict(prunedtree, test, type = "vector")
MSE2pruned <- mean((test$pruned - test$Collection)^2)
MSE2pruned[1] 93731913
Para los árboles de clasificación, utilizamos el modo, donde, la categoría más frecuente en esa región será la predicción.
Tanto la regresión como la clasificación utilizan la división binaria recursiva
En la regresión se utiliza el RSS para decidir la división
En la clasificación podemos utilizar
Tasa de error de clasificación
Índice de Gini
Entropía cruzada
Ahora para verlo en un ejemplo utilizaremos el mismo dataset de las
películas, solo que ahora queremos ver clasificar las películas de
acuerdo si ganarán un Oscar por medio de la variable binaria
Star_Tech_Oscar
Ahora veamos cómo realizamos un árbol de clasificación desde R.
mc <- read.csv("Movie_classification.csv")
head(mc)summary(mc) Marketing.expense Production.expense Multiplex.coverage
Min. : 20.13 Min. : 55.92 Min. :0.1290
1st Qu.: 21.64 1st Qu.: 65.38 1st Qu.:0.3760
Median : 25.13 Median : 74.38 Median :0.4620
Mean : 92.27 Mean : 77.27 Mean :0.4453
3rd Qu.: 93.54 3rd Qu.: 91.20 3rd Qu.:0.5510
Max. :1799.52 Max. :110.48 Max. :0.6150
Budget Movie_length Lead_.Actor_Rating
Min. :19781 Min. : 76.4 Min. :3.840
1st Qu.:32694 1st Qu.:118.5 1st Qu.:7.316
Median :34488 Median :151.0 Median :8.307
Mean :34911 Mean :142.1 Mean :8.014
3rd Qu.:36794 3rd Qu.:167.6 3rd Qu.:8.865
Max. :48773 Max. :173.5 Max. :9.435
Lead_Actress_rating Director_rating Producer_rating Critic_rating
Min. :4.035 Min. :3.840 Min. :4.030 Min. :6.600
1st Qu.:7.504 1st Qu.:7.296 1st Qu.:7.508 1st Qu.:7.200
Median :8.495 Median :8.312 Median :8.465 Median :7.960
Mean :8.186 Mean :8.020 Mean :8.191 Mean :7.811
3rd Qu.:9.030 3rd Qu.:8.884 3rd Qu.:9.030 3rd Qu.:8.260
Max. :9.540 Max. :9.425 Max. :9.635 Max. :9.400
Trailer_views X3D_available Time_taken
Min. :212912 Length:506 Min. : 0.0
1st Qu.:409128 Class :character 1st Qu.:132.3
Median :462460 Mode :character Median :160.0
Mean :449861 Mean :157.4
3rd Qu.:500248 3rd Qu.:181.9
Max. :567784 Max. :217.5
NA's :12
Twitter_hastags Genre Avg_age_actors Num_multiplex
Min. : 201.2 Length:506 Min. : 3.00 Min. :333.0
1st Qu.: 223.8 Class :character 1st Qu.:28.00 1st Qu.:465.0
Median : 254.4 Mode :character Median :39.00 Median :535.5
Mean : 260.8 Mean :39.18 Mean :545.0
3rd Qu.: 283.4 3rd Qu.:50.00 3rd Qu.:614.8
Max. :2022.4 Max. :60.00 Max. :868.0
Collection Start_Tech_Oscar
Min. : 10000 Min. :0.0000
1st Qu.: 34050 1st Qu.:0.0000
Median : 42400 Median :1.0000
Mean : 45058 Mean :0.5455
3rd Qu.: 50000 3rd Qu.:1.0000
Max. :100000 Max. :1.0000
Podemos ver que la variable Time_taken tiene valores en
NA, esto nos va a generar problemas en el momento que realicemos las
corridas de cualquier modelo en R. Para eliminar estos NA realizamos el
siguiente procedimiento.
mc$Time_taken[is.na(mc$Time_taken)] <- mean(mc$Time_taken,na.rm = TRUE)Ya que hemos limpiado los datos, procedemos a realizar la separación
de nuestros conjuntos Test y Train, sin olvidar que si, no hemos
ejecutado la librería caTools debemos de hacerlo por medio
de l comando library(caTools):
set.seed(0)
split <- sample.split(mc,SplitRatio = 0.8)
trainc <- subset(mc,split == TRUE)
testc <- subset(mc,split == FALSE)Ahora, utilizando el paquete de rpart, vamos a encontrar
el árbol de clasificación, cambiando el método por
class.
# Modelo de clasificación
classtree <- rpart(formula = Start_Tech_Oscar~.,
data = trainc,
method = "class",
control = rpart.control(maxdepth = 3))
rpart.plot(classtree, box.palette="RdBu", digits = -3)De igual forma, podemos buscar el predictor, evaluando en nuestro modelo en los datos de test:
testc$pred <- predict(classtree, testc, type="class")
testc %>% select(Start_Tech_Oscar,
pred)Podemos ver y comparar cómo está clasificando nuestro modelo y vemos que solo en esta simple visualización no tenemos todos los datos de clasificados de forma correcta.
Para comparar como se está comportando en forma general nuestro modelos de clasificación por medio de un árbol de decisión, una forma es comparar gráficamente los valores actuales junto con los que se predicen.
Hagamos una comparación creando una tabla:
table(testc$Start_Tech_Oscar,testc$pred)
0 1
0 45 5
1 38 19
Esta matriz (la famosa matriz de
confusión), lo que nos indica es que para el primer caso,
la diagonal nos mostrará el número de valores que se predijeron
correctamente. Por ejemplo las películas que no tuvieron Oscar
(Start_Tech_Oscar== 0), el modelo encontró 45 correctas de
50 (45+5), mientras que para las películas que tuvieron Oscar, el modelo
encontró 19 correctas de 57 (38+19).
Así que cuando la película es realmente ganar el Oscar tenemos una muy mala precisión de predicción (accuracy). Mientras que cuando la película no gana el Oscar tenemos una muy buena precisión de predicción.
Si queremos saber cuanto exactamente es nuestro accuracy en % podemos realizarlo por medio la división: los que acertó/todos:
(45+19)/(45+5+38+19)*100[1] 59.81308
https://www.rdocumentation.org/packages/rpart/versions/4.1-15
https://rpubs.com/jboscomendoza/arboles_decision_clasificacion
Decision Trees, Random Forests, Bagging & XGBoost: R Studio, udemy.